﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using ValidationFramework.Configuration.Fluent;
using ValidationFramework.Reflection;

namespace ValidationFramework.Tests.Fluent
{
	#region Test Model

	public class Person
	{
		public string FirstName { get; set; }
		public string LastName { get; set; }
		public int Age { get; set; }

		public Person()
		{

		}
	}

	#endregion

	#region Test Generic Validator and Extensions

	// --- Validator
	public class TestGenericValidator<TToValidate> : SpecificationBase
	{
		public TToValidate TestGenericProperty { get; set; }

		public override bool IsSatisfiedBy(object targetToValidate)
		{
			return true;
		}
	}

	// --- Validator Selection Section
	public class TestGenericValidatorOptions<TValidating> : DefaultSpecificationConfigurationStep<TestGenericValidator<TValidating>, TValidating>
	{
		// Required Constructor
		public TestGenericValidatorOptions(RuleCreationStep<TValidating> fluentContext, TestGenericValidator<TValidating> validator)
			: base(fluentContext, validator)
		{
		}

		public TestGenericValidatorOptions<TValidating> GenericValidatorOption(TValidating someTypedValue)
		{
			Specification.TestGenericProperty = someTypedValue;
			return this;
		}
	}

	// --- Validator Options Section
	public static class TestGenericValidatorFluentExtensions
	{
		public static TestGenericValidatorOptions<TValidating> PassesTestGenericValidator<TValidating>(this RuleCreationStep<TValidating> self)
		{
			var validator = new TestGenericValidator<TValidating>();

			return new TestGenericValidatorOptions<TValidating>(self, validator);
		}
	}

	#endregion

	#region Test Validator and Extensions

	// --- Validator
	public class TestValidator : SpecificationBase
	{
		public int TestProperty { get; set; }

		public override bool IsSatisfiedBy(object targetToValidate)
		{
			return true;
		}
	}

	// --- Validator Selection Section
	public class TestValidatorOptions<TValidating> : DefaultSpecificationConfigurationStep<TestValidator, TValidating>
	{
		// Required Constructor
		public TestValidatorOptions(RuleCreationStep<TValidating> fluentContext, TestValidator validator)
			: base(fluentContext, validator)
		{
		}

		public TestValidatorOptions<TValidating> FluentOption(int someInteger)
		{
			Specification.TestProperty = someInteger;
			return this;
		}
	}

	// --- Validator Options Section
	public static class TestValidatorFluentExtensions
	{
		public static TestValidatorOptions<TValidating> PassesTestValidator<TValidating>(this RuleCreationStep<TValidating> self)
		{
			var validator = new TestValidator();

			return new TestValidatorOptions<TValidating>(self, validator);
		}
	}

	#endregion



	[TestFixture]
	public class FluentTests
	{
		public class CompileTests
		{
			[Test]
			public void Should_Compile_When_Using_Generic_Validator()
			{
				var typePolicy = new TypePolicy();
				var fluent = new RuleLocationStep<Person>(typePolicy);

				// simple
				fluent.Ensure(x => x.FirstName)
					.Passes(new TestValidator());

				// complex
				fluent.EnsureParentOf(x => x.LastName)
					.Passes(new TestValidator())
					.Priority(10)
					.OnFailure()
						.Severity(3)
						.UseMessage("Requried!")
						.UseErrorMessageProvider();

				// custom

			}

			[Test]
			public void Should_Compile_When_Using_Test_Generic_Validator_Extensions()
			{
				var typePolicy = new TypePolicy();
				var fluent = new RuleLocationStep<Person>(typePolicy);

				// member
				fluent.Ensure(x => x.FirstName)
					.PassesTestGenericValidator()
						.GenericValidatorOption("Test");

				// parent
				var person = new Person();
				fluent.EnsureParentOf(x => x.LastName)
					.PassesTestGenericValidator()
						.GenericValidatorOption(person);
			}

			[Test]
			public void Should_Compile_When_Using_Test_Validator_Extensions()
			{
				var typePolicy = new TypePolicy();
				var fluent = new RuleLocationStep<Person>(typePolicy);

				// member
				fluent.Ensure(x => x.FirstName)
					.PassesTestValidator()
						.FluentOption(10);

				// parent
				fluent.EnsureParentOf(x => x.LastName)
					.PassesTestValidator()
						.FluentOption(10);

			}

			[Test]
			public void Should_Honor_Contraints()
			{
				// if we added type constraints to the extensions or options
				// the fluent interface should be hidden


				// We can't write a test for this since it won't compile.
			}

			[Test]
			public void Should_Type_Generic_Validator_To_Parent_Type()
			{
				var typePolicy = new TypePolicy();
				var fluent = new RuleLocationStep<Person>(typePolicy);
				var person = new Person();
				fluent.EnsureParentOf(x => x.LastName)
					.PassesTestGenericValidator()
						.GenericValidatorOption(person); // <-- this line should compile
			}

		}
		[TestFixture]
		public class AddRule
		{
			[Test]
			public void Should_Add_Rule_Which_Uses_Specified_Validator()
			{
				// Arrange
				Validation.Initialize();
				var originalPolicy = Validation.GetValidationPolicy<Person>(property => property.Age);
				Assert.AreEqual(0, originalPolicy.RuleCount);

				// Act
				Validation.ConfigurePolicyFor<Person>(x => 
				{
					x.Ensure(property => property.Age)
							.PassesTestValidator()
								.FluentOption(10);
				});

				// Assert
				var policy = Validation.GetValidationPolicy<Person>(property => property.Age);
				Assert.AreEqual(1, policy.RuleCount);
	
				var rule = policy.MemberScopeRules[0];
				var validator = (TestValidator)rule.Specification;
				Assert.AreEqual(10, validator.TestProperty);
			}

			[Test]
			public void Should_Add_Rule_To_Framework()
			{
				// Arrange
				Validation.Initialize();
				var originalPolicy = Validation.GetValidationPolicy<Person>(property => property.Age);
				Assert.AreEqual(0, originalPolicy.RuleCount);

				// Act
				Validation.ConfigurePolicyFor<Person>(x =>
				{
					x.Ensure(property => property.Age)
							.PassesTestValidator();
				});

				// Assert
				var policy = Validation.GetValidationPolicy<Person>(property => property.Age);
				Assert.AreEqual(1, policy.RuleCount);
			}

			[Test]
			public void Should_Not_Add_Rule_To_Framework_When_No_Validator_Specified()
			{
				// Arrange
				Validation.Initialize();
				var originalPolicy = Validation.GetValidationPolicy<Person>(property => property.Age);
				Assert.AreEqual(0, originalPolicy.RuleCount);

				// Act
				Validation.ConfigurePolicyFor<Person>(x =>
				{
					x.Ensure(property => property.Age);
				});

				// Assert
				var policy = Validation.GetValidationPolicy<Person>(property => property.Age);
				Assert.AreEqual(0, policy.RuleCount);
			}

			[Test]
			public void Should_Update_Rule_When_Using_Rule_Options()
			{
				// Arrange
				Validation.Initialize();
				var originalPolicy = Validation.GetValidationPolicy<Person>(property => property.Age);
				Assert.AreEqual(0, originalPolicy.RuleCount);

				// Act
				Validation.ConfigurePolicyFor<Person>(x =>
				{
					x.Ensure(property => property.Age)
						.PassesTestValidator()
							.FluentOption(10)
						.OnFailure()
							.UseMessage("This works");

				});

				// Assert
				var policy = Validation.GetValidationPolicy<Person>(property => property.Age);
				Assert.AreEqual(1, policy.RuleCount);

				Assert.AreEqual("This works", policy.MemberScopeRules[0].ErrorMessage);
			}
		}
	}
}
